package br.org.bertol.mestrado.engine.optimisation.entity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.fitness.AbstractCombinatorialFitness;
import br.org.bertol.mestrado.engine.fitness.Objective;

/**
 * Casse abstrat q emcapsula uma unidade de caminh a ser melhorada.
 * @author contaqualquer
 * @param <T>
 *            Tipo
 */
public abstract class AbstractUnit<T> implements Cloneable {

    /***/
    protected transient ArrayList<T>                    route;

    /**
     */
    protected transient EnumMap<Objective, Number>      height;

    /***/
    protected transient AbstractCombinatorialFitness<T> abstractCombinatorialFitness;

    /**
     */
    private transient int                               controlePermuta;

    /**
     */
    private transient int                               pos1;

    /**
     */
    private transient int                               pos2;

    /**
     */
    private boolean                                     dominated;

    /**
     * Cria uma unidade a ser melhorada pelo processo de otimização.
     * @param fitness
     *            Tipo de cálculo
     * @param objectives
     *            Lista de objetivos
     */
    public AbstractUnit(final AbstractCombinatorialFitness<T> fitness,
        final Objective... objectives) {
        super();

        route = new ArrayList<T>();

        height = new EnumMap<Objective, Number>(Objective.class);

        controlePermuta = 0;

        pos1 = 0;

        pos2 = 0;

        for (Objective objective : objectives) {
            height.put(objective, 0);
        }

        abstractCombinatorialFitness = fitness;
    }

    /**
     * @param newRoute
     *            the route to set
     */
    @SuppressWarnings("unchecked")
    public final void setRoute(final List<T> newRoute) {
        this.route = (ArrayList<T>) ((ArrayList<T>) newRoute)
                .clone();
    }

    /**
     * @param <A>
     *            Tipo de array
     * @param array
     *            Array
     * @return Um array contendo todos os elementos do caminho
     */
    public final <A> A[] toArray(final A[] array) {
        return route.toArray(array);
    }

    /**
     * Ajusta o peso de um objetivo com um valor.
     * @param objective
     *            Objetivo
     * @param newHeight
     *            Valor
     */
    public final void setHeight(final Objective objective,
            final int newHeight) {
        this.height.put(objective, newHeight);
    }

    /**
     * @return Peso da unidade
     */
    public final EnumMap<Objective, Number> getHeight() {
        return height;
    }

    /**
     * @param sum
     *            Ajuste de pesos da unidade
     */
    public final void sumHeigtht(final EnumMap<Objective, Number> sum) {

        // percorre todos os objetivos do problema adicionando novos
        // valores para os pesos
        for (final Objective objective : height.keySet()) {
            height.put(objective, height.get(objective).intValue()
                    + sum.get(objective).intValue());
        }
    }

    /**
     * @param item
     *            Classe a ser adicionada
     * @return true caso tenho ocorrido adição, falso caso contrário
     * @see java.util.List#contains(Object)
     */
    public final boolean addClass(final T item) {
        // evitar adicionar a mesma clase duas vezes no mesmo caminho
        if (route.contains(item)) {
            return false;
        }

        return route.add(item);
    }

    /**
     * @param element
     *            Classe a ser removida
     * @return true caso tenho ocorrido remo��o, falso caso contr�rio
     * @see java.util.List#remove(int)
     */
    public final boolean removeClass(final T element) {
        return route.remove(element);
    }

    /**
     * @param index
     *            Indice da classe a sre removida
     * @return true caso tenho ocorrido remoção, falso caso contr�rio
     * @see java.util.List#remove(int)
     */
    public final T removeClass(final int index) {
        return route.remove(index);
    }

    /**
     * Rota obtida pela unidade.
     * @return {@link ArrayList}
     */
    public final ArrayList<T> getRoute() {
        return route;
    }

    /**
     * Atualiza o status da unidade.
     */
    public abstract void update();

    /**
     * Calcula o fitness da unidade.
     * @return {@link Boolean} true caso tenha ocorrido tudo ok, false caso
     *         contrároi
     */
    public final boolean calcFitness() {
        return abstractCombinatorialFitness.fitness(route, height);
    }

    /**
     * Realiza uma melhora no conjunto atual de acordo com o problema
     * implementado.
     * @param <U>
     *            Tipo de objeto
     * @param unit
     *            Unidade a ser melhorada
     * @param info
     *            TODO
     * @return {@link AbstractUnit} Unidade melhorada, se possível, ou a mesma
     *         no pior caso
     */
    public final <U extends AbstractUnit<T>> U optimal(final U unit,
            final Object... info) {

        try {
            return abstractCombinatorialFitness.doBetter(unit, info);
        } catch (Exception e) {
            Logger.getLogger(getClass()).error("Erro ao aprimorar solução.", e);
        }

        return unit;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public Object clone() throws CloneNotSupportedException {

        final AbstractUnit<T> tmp = (AbstractUnit<T>) super.clone();

        tmp.route = (ArrayList<T>) this.route.clone();

        tmp.height = this.height.clone();

        tmp.abstractCombinatorialFitness =
                (AbstractCombinatorialFitness<T>) this.abstractCombinatorialFitness
                .clone();

        return tmp;
    }

    /**
     *{@inheritDoc}
     */
    public String toString() {
        final StringBuilder buffer = new StringBuilder();

        buffer.append("Valor");

        for (final Objective objective : height.keySet()) {
            buffer.append(" f(").append(objective.name()).append(") : ");

            buffer.append("[").append(
                                      objective.toString(height
                    .get(objective))).append("] | ");

        }
        buffer.append("Fitness count : [").append(
                                                  abstractCombinatorialFitness
                .getFitnessCount()).append("]");

        buffer.append(" | ");

        buffer.append("Conjunto encontrado : ").append(route);

        buffer.append(" | ");

        buffer.append(abstractCombinatorialFitness.toString());

        return buffer.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((height == null) ? 0 : height.hashCode());
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof AbstractUnit<?>)) {
            return false;
        }
        AbstractUnit<?> other = (AbstractUnit<?>) obj;
        if (height == null) {
            if (other.height != null) {
                return false;
            }
        } else if (!height.equals(other.height)) {
            return false;
        }
        return true;
    }

    /**
     * @param x
     *            Posição atual do ponteiro
     */
    public final void setControlePermuta(final int x) {
        controlePermuta = x;
    }

    /**
     * @return Posição 1
     */
    public final int getX1() {
        return pos1;
    }

    /**
     * @param pos
     *            Entreada
     */
    public final void setX1(final int pos) {
        this.pos1 = pos;
    }

    /**
     * @return Posição 2
     */
    public final int getX2() {
        return pos2;
    }

    /**
     * @param pos
     *            Entreada
     */
    public final void setX2(final int pos) {
        this.pos2 = pos;
    }

    /**
     * Retorna a posição atual de permutação.
     * @return Índice atual da permutação
     */
    public final int getControlePermuta() {
        return controlePermuta;
    }

    /**
     * Desfaz movimento.
     */
    public final void undoneMove() {
        Collections.swap(route, pos2, pos1);
    }

    /**
     * @return Dominado true ou false
     */
    public final boolean isDominated() {
        return dominated;
    }

    /**
     * @param dominate
     *            Dominado true ou false
     */
    public final void setDominated(final boolean dominate) {
        this.dominated = dominate;
    }

    /**
     * Consolida resultado.
     * @return Resultado consolidado
     */
    public final StoreResult<T> getConsolidateResult() {

        try {

            return new StoreResult<T>(route, height,
                    abstractCombinatorialFitness);

        } catch (CloneNotSupportedException e) {
            Logger.getLogger(getClass()).error("Erro ao consolidar resultado",
                                               e);
        }

        return new StoreResult<T>();

    }
}